<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="coinfaces">

        <title>为什么创建守护进程需要两次fork()</title>

    <link rel="stylesheet" href="../theme/css/style.css">
    <link rel="stylesheet" href="../theme/css/pygments.css">



</head>

<body>
    <div class="content">
<h1>为什么创建守护进程需要两次fork()</h1>    <p>
        under
    </p>
    <p>
        in <a href="../categories/draft/">draft</a>
    </p>
    <p>Published: 2020-02-27</p>


    <p>常被提及的理由是：</p>
<blockquote>
<p>避免守护进程被终端发送的信号杀死，同时也避免产生僵尸进程，这只需要fork一次，因此不准确。</p>
</blockquote>
<p>目前查到的最合理的说法是为了避免<strong>意外</strong>获得控制终端（Controlling Terminal）。根据<a href="http://thelinuxjedi.blogspot.com/2014/02/why-use-double-fork-to-daemonize.html">这篇博文</a>，这个过程如下：</p>
<ul>
<li>父进程<code>fork()</code>，产生确保不是session leader或者process group leader的子进程，父进程退出</li>
<li>子进程调用<code>setsid()</code>，产生新的session并成为其leader，并切断和当前终端的联系</li>
<li>子进程<code>fork()</code>，产生非session leader的第二代子进程，第一代子进程退出，第二代子进程成为守护进程</li>
</ul>
<p>更详细的解释是，第一代子进程的目的是为了调用<code>setsid()</code>切断和当前终端的联系，如果父进程不是session leader或者process group leader，它也可以做这个工作，但是由于父进程是别的进程<code>fork()</code>产生的，它不确定，直接<code>fork()</code>一个子进程来做这件事是比较方便的。</p>
<p>第二代子进程的目的是为了避免<strong>意外</strong>获得控制终端（不确定意外发生的条件，也许是守护进程往终端写数据的调试语句没有处理干净），要确保这种情况不会发生，进程不能是session leader，第一代子进程调用<code>setsid()</code>之后即成为session leader，所以还需要一次<code>fork()</code>。</p>
<p>子进程意外获得控制终端的可能性很大程度上来自于<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html#tag_11_01_03">POSIX规范</a>的模糊表述，因为根据控制终端和会话的关系，子进程重新获得控制终端并不容易。并且很多时候可以确定守护进程不会打开终端（它确实不应该这么做）。因此通过两次<code>fork()</code>来创建守护进程并非必要，只是避免麻烦的简便做法。</p>
<p>其它问题：</p>
<ol>
<li>第二代子进程是否可以调用<code>setsid()</code>并有意为之，这不重要，因为不属于<strong>意外</strong>。</li>
<li>Session、process group及其leader存在的意义是什么，<a href="http://0pointer.de/blog/projects/pgrp-vs-session.html">似乎Systemd的作者也在疑惑这件事</a>。</li>
<li>控制终端可以理解成用户界面和进程通信的桥梁。Linux中每个进程都属于某个会话，有些会话会和终端发生关联，从而用户可以通过这个终端来控制会话中的进程，包括发送Ctrl+C终止程序运行，调整窗口大小改变TUI应用界面布局，这个终端就是会话的控制终端。已互相关联的会话和控制终端是一一对应的，因此改变进程的控制终端很不容易，神奇的是有<a href="https://github.com/nelhage/reptyr">程序</a>能做这件事，另有<a href="https://blog.nelhage.com/2011/02/changing-ctty/">文章</a>解释它是怎么做的。</li>
<li>进程无法通过两次fork脱离cgroup，这样的事实巧合给监控守护进程的工作创造了便利。但cgroup的主要目的是资源配额管理，而fork的目的也不是为了躲避监控，两者其实关系不大。</li>
</ol>
    </div>
<nav class="nav sidebar">
    <div class="siteinfo">
        <div class="sitename"><a href="..">coinfaces</a></div>
        <div class="tagline">Another Side of Life</div>
    </div>

        <a href="../categories/">categories</a>
        <a href="..">&larr; Home</a>
</nav><footer class="footer">
    <span>
        &copy; jpli &middot;
        under <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA 4.0</a>
    </span>
    |
    <span>
        Powered by:
        <a href="http://blog.getpelican.com/">Pelican</a>
    </span>
</footer></body>
</html>